﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Linq;
using Microsoft.SharePoint.WebControls;
using ZhukBlogLinqExamples.Model;

namespace ZhukBlogLinqExamples
{
    public static class Extensions
    {
        /// <summary>
        /// Получение значения своства
        /// </summary>
        /// <typeparam name="T">Тип свойства</typeparam>
        /// <param name="obj">Объект</param>
        /// <param name="propName">Имя свойства</param>
        public static T GetPropertyValue<T>(this object obj, string propName)
        {
            // Получаем тип
            var type = obj.GetType();
            // Получаем свойство
            var prop = type.GetProperty(propName);
            // Получаем значение
            var val = prop.GetValue(obj, null);
            // Приводим к типу T или, если это невозможно,
            // возвращаем значение по умолчанию для типа T
            return val is T
                ? (T)val
                : default(T);
        }

        public static Expression<Func<T, bool>> EqualsAny<T, TValue>(this Expression<Func<T, TValue>> selector, IEnumerable<TValue> values)
        {
            // Если значений нет, то возвращаем выражение x=> false
            if (!values.Any()) return x => false;
            // Аналогично поступаем в случае, когда кол-во параметров не равно одному
            if (selector.Parameters.Count != 1) return x => false;
            // Берем параметр селектора.
            // Он понадобиться для построения выражений
            var p = selector.Parameters.First();
            // Для каждого значения строим выражение
            var equals = values
                .Select(v => (Expression)Expression.Equal(selector.Body, Expression.Constant(v, typeof(TValue))));
            // Объдиняем получившиеся выражения
            var body = equals.Aggregate(Expression.Or);
            // Возвращаям получившиеся выражение
            return Expression.Lambda<Func<T, bool>>(body, p);
        }

        public static Expression<Func<T, bool>> NotEqualsAny<T, TValue>(this Expression<Func<T, TValue>> selector, IEnumerable<TValue> values)
        {
            if (!values.Any()) return x => false;
            if (selector.Parameters.Count != 1) return x => false;
            var p = selector.Parameters.First();
            var equals = values
                .Select(v => (Expression)Expression.NotEqual(selector.Body, Expression.Constant(v, typeof(TValue))));
            var body = equals.Aggregate(Expression.Or);
            return Expression.Lambda<Func<T, bool>>(body, p);
        }

        public static Expression<Func<T, bool>> NotEqualsAll<T, TValue>(this Expression<Func<T, TValue>> selector, IEnumerable<TValue> values)
        {
            if (!values.Any()) return x => false;
            if (selector.Parameters.Count != 1) return x => false;
            var p = selector.Parameters.First();
            var equals = values
                .Select(v => (Expression)Expression.NotEqual(selector.Body, Expression.Constant(v, typeof(TValue))));
            var body = equals.Aggregate(Expression.And);
            return Expression.Lambda<Func<T, bool>>(body, p);
        }

        public static Expression<Func<T, bool>> ContainsAny<T>(this Expression<Func<T, string>> selector,
            IEnumerable<string> values)
        {
            if (!values.Any()) return x => false;
            if (selector.Parameters.Count != 1) return x => false;
            var p = selector.Parameters.First();
            var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
            var equals = values
                .Select(v => (Expression)Expression.Call(
                    selector.Body, method, Expression.Constant(v, typeof(string))));
            var body = equals.Aggregate(Expression.Or);
            return Expression.Lambda<Func<T, bool>>(body, p);
        }

        public static Expression<Func<T, bool>> ContainsAny<T>(this Expression<Func<T, string[]>> selector,
            IEnumerable<string> values)
        {
            if (!values.Any()) return x => false;
            if (selector.Parameters.Count != 1) return x => false;
            var p = selector.Parameters.First();
            var method = typeof(string).GetMethod("Contains", new[] { typeof(string[]) });
            var equals = values
                .Select(v => (Expression)Expression.Call(
                    selector.Body, method, Expression.Constant(v, typeof(string[]))));
            var body = equals.Aggregate(Expression.Or);
            return Expression.Lambda<Func<T, bool>>(body, p);
        }

        public static Expression<Func<T, bool>> StartsWithAny<T>(this Expression<Func<T, string>> selector,
            IEnumerable<string> values)
        {
            if (!values.Any()) return x => true;
            if (selector.Parameters.Count != 1) return x => true;
            var p = selector.Parameters.First();
            var method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
            var equals = values.Select(v => (Expression)Expression.Call(
                selector.Body, method, Expression.Constant(v, typeof(string))));
            var body = equals.Aggregate(Expression.Or);
            return Expression.Lambda<Func<T, bool>>(body, p);
        }

        public static string GetChoiceValue(this Enum enumerator)
        {
            // Получаем тип
            var type = enumerator.GetType();
            // Получаем имя поля
            var fieldName = Enum.GetName(enumerator.GetType(), enumerator);
            // Получаем поле
            var field = type.GetField(fieldName, BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public);
            // Получаем атрибуты найденного поля
            var attributes = field.GetCustomAttributes(typeof(ChoiceAttribute), true);
            var attribute = attributes.FirstOrDefault();
            // Если атрибута нет, то возвращаем пустую строку
            // Иначе возвращаем значение Value атрибута
            return attribute == null
                        ? string.Empty
                        : ((ChoiceAttribute)attribute).Value;
        }

        /// <summary>
        /// Получение уникальных значений поля списка
        /// </summary>
        /// <typeparam name="TValue">Тип поля</typeparam>
        /// <param name="field">Поле списка</param>
        /// <returns></returns>
        public static IEnumerable<TValue> DistinctValues<TValue>(this SPField field)
        {
            var res = new List<TValue>();
            SPSecurity.RunWithElevatedPrivileges(
                () =>
                {
                    //Получаем ID сайта
                    var siteId = field.ParentList.ParentWeb.Site.ID;
                    // Инициализируем новый сайт с правами учетной записи пула приложения
                    string connectionString;
                    using (var site = new SPSite(siteId))
                    {
                        connectionString = site.ContentDatabase.DatabaseConnectionString;
                    }
                    // Получаем значение атрибута ColName
                    var colName = field.AttributeValue("ColName");
                    var ordinal = field.AttributeValueAsInteger("RowOrdinal");
                    var listId = field.ParentList.ID;
                    // Создаем комманду
                    using (var cmd = new SqlCommand(FieldDistinctValues.Replace("%SqlColName%", colName))
                            {
                                CommandType = CommandType.Text
                            })
                    {
                        cmd.Parameters.Add(new SqlParameter("@ListId", listId));
                        cmd.Parameters.Add(new SqlParameter("@RowOrdinal", ordinal));
                        // Инициализируем подключение к базе данных
                        using (var con = new SqlConnection(connectionString))
                        {
                            cmd.Connection = con;
                            con.Open();
                            var reader = cmd.ExecuteReader();
                            while (reader != null && reader.Read())
                            {
                                // Заполняем полученные значения
                                res.Add(reader[0] is TValue ? (TValue)reader[0] : default(TValue));
                            }
                        }
                        if (cmd.Connection.State != ConnectionState.Closed)
                        {
                            cmd.Connection.Close();
                        }
                    }
                });
            return res;
        }

        public static Dictionary<TValue, long> DistinctValuesQnt<TValue>(this SPField field)
        {
            var res = new Dictionary<TValue, long>();
            SPSecurity.RunWithElevatedPrivileges(
                () =>
                {
                    var siteId = field.ParentList.ParentWeb.Site.ID;
                    string connectionString;
                    using (var site = new SPSite(siteId))
                    {
                        connectionString = site.ContentDatabase.DatabaseConnectionString;
                    }
                    var colName = field.AttributeValue("ColName");
                    var ordinal = field.AttributeValueAsInteger("RowOrdinal");
                    var listId = field.ParentList.ID;// new Guid(field.AttributeValue("SourceID"));
                    using (var cmd = new SqlCommand(FieldDistinctValuesQnt.Replace("%SqlColName%", colName))
                                         {
                                             CommandType = CommandType.Text
                                         })
                    {
                        cmd.Parameters.Add(new SqlParameter("@ListId", listId));
                        cmd.Parameters.Add(new SqlParameter("@RowOrdinal", ordinal));
                        using (var con = new SqlConnection(connectionString))
                        {
                            cmd.Connection = con;
                            con.Open();
                            var reader = cmd.ExecuteReader();
                            while (reader != null && reader.Read())
                            {
                                var key = reader.IsDBNull(0) ? default(TValue) : (TValue)reader.GetValue(0);
                                var val = reader.GetInt32(1);
                                res.Add(key, val);
                            }
                        }
                        if (cmd.Connection.State != ConnectionState.Closed)
                        {
                            cmd.Connection.Close();
                        }
                    }
                });
            return res;
        }

        public static string AttributeValue(this SPField field, string attrName)
        {
            var xml = new XmlDocument();
            xml.LoadXml(field.SchemaXml);
            return xml.DocumentElement.GetAttributeValue(attrName);
        }

        public static int AttributeValueAsInteger(this SPField field, string attrName)
        {
            var xml = new XmlDocument();
            xml.LoadXml(field.SchemaXml);
            return xml.DocumentElement.GetAttributeValueAsInteger(attrName);
        }

        public static int GetAttributeValueAsInteger(this XmlNode node, string attrName)
        {
            int num;
            string attributeValue = node.GetAttributeValue(attrName);
            if (attributeValue.IndexOf('.') != -1)
            {
                attributeValue = attributeValue.Substring(0, attributeValue.IndexOf('.'));
            }
            int.TryParse(attributeValue, out num);
            return num;
        }

        public static string GetAttributeValue(this XmlNode node, string attrName)
        {
            return ((node.Attributes[attrName] == null) ? string.Empty : node.Attributes[attrName].Value);
        }

        public static PickerEntity ToPickerEntity(this SPFieldLookupValue value)
        {
            return new PickerEntity
                       {
                           DisplayText = value.LookupValue,
                           IsResolved = true,
                           EntityType = "Employee",
                           Key = value.LookupId.ToString()
                       };
        }

        public static PickerEntity ToPickerEntity(this ZhukDataItem value)
        {
            return new PickerEntity
                        {
                            DisplayText = value.Title,
                            IsResolved = true,
                            EntityType = "ZhukDataItem",
                            Key = value.Id.ToString()
                        };
        }

        public static XmlNode ViewFields(this ZhukDataItem item)
        {
            var objType = item.GetType();
            var properties = objType.GetProperties();
            var res = new XElement("ViewFields");
            foreach (var property in properties)
            {
                var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
                foreach (ColumnAttribute att in attributes)
                {
                    if (att.IsLookupValue)
                        continue;
                    res.Add(new XElement("FieldRef", new XAttribute("Name", "ows_" + att.Name)));
                }
            }
            return res.GetXmlNode();
        }

        public static XElement GetXElement(this XmlNode node)
        {
            var xDoc = new XDocument();
            using (var xmlWriter = xDoc.CreateWriter())
                node.WriteTo(xmlWriter);
            return xDoc.Root;
        }

        public static XmlNode GetXmlNode(this XElement element)
        {
            using (var xmlReader = element.CreateReader())
            {
                var xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlReader);
                return xmlDoc;
            }
        }

        public static string FieldDistinctValues =
                @"
            select distinct
	            %SqlColName%
            from
	            dbo.AllUserData
            where
	            tp_ListId = @ListId
	            and tp_IsCurrent = 1
                and                    
	            tp_RowOrdinal = @RowOrdinal
	            and
	            tp_DeleteTransactionId = 0x
            order by
                1 asc";

        public static string FieldDistinctValuesQnt =
                @"
                select
	                %SqlColName%,
                    count(*) qnt
                from
	                dbo.AllUserData (nolock)
                where
	                tp_ListId = @ListId
	                and tp_IsCurrent = 1
                    and                    
	                tp_RowOrdinal = @RowOrdinal
	                and
	                tp_DeleteTransactionId = 0x
                group by
                    %SqlColName%
                --order by
                --    1 asc";
    }
}
